From e859600ceaec1acd946ba7e1942ba2d9f25f6311 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Sun, 1 Oct 2006 20:17:16 +0000 Subject: [PATCH] * API: pageSet now supports pageids, revised revisions listings, lots of examples. --- includes/api/ApiBase.php | 70 +++++------ includes/api/ApiFormatJson.php | 2 +- includes/api/ApiFormatXml.php | 34 +++-- includes/api/ApiFormatYaml.php | 2 +- includes/api/ApiHelp.php | 100 +++++++-------- includes/api/ApiLogin.php | 2 +- includes/api/ApiMain.php | 10 +- includes/api/ApiPageSet.php | 193 ++++++++++++++++++++--------- includes/api/ApiQuery.php | 155 ++++++++++++++--------- includes/api/ApiQueryAllpages.php | 22 ++-- includes/api/ApiQueryBase.php | 193 +++++++++++++++-------------- includes/api/ApiQueryInfo.php | 46 ++++--- includes/api/ApiQueryRevisions.php | 125 +++++++++++-------- includes/api/ApiQuerySiteinfo.php | 10 +- includes/api/ApiResult.php | 40 +++--- 15 files changed, 571 insertions(+), 433 deletions(-) diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index d0aab377ca..c145b1df4f 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -70,8 +70,7 @@ abstract class ApiBase { // Main module has getResult() method overriden // Safety - avoid infinite loop: if ($this->isMain()) - ApiBase :: dieDebug(__METHOD__ . - ' base method was called on main module. '); + ApiBase :: dieDebug(__METHOD__, 'base method was called on main module. '); return $this->getMain()->getResult(); } @@ -189,9 +188,9 @@ abstract class ApiBase { $multi = false; $type = gettype($paramSettings); } else { - $default = isset ($paramSettings[self::PARAM_DFLT]) ? $paramSettings[self::PARAM_DFLT] : null; - $multi = isset ($paramSettings[self::PARAM_ISMULTI]) ? $paramSettings[self::PARAM_ISMULTI] : false; - $type = isset ($paramSettings[self::PARAM_TYPE]) ? $paramSettings[self::PARAM_TYPE] : null; + $default = isset ($paramSettings[self :: PARAM_DFLT]) ? $paramSettings[self :: PARAM_DFLT] : null; + $multi = isset ($paramSettings[self :: PARAM_ISMULTI]) ? $paramSettings[self :: PARAM_ISMULTI] : false; + $type = isset ($paramSettings[self :: PARAM_TYPE]) ? $paramSettings[self :: PARAM_TYPE] : null; // When type is not given, and no choices, the type is the same as $default if (!isset ($type)) { @@ -205,7 +204,7 @@ abstract class ApiBase { if ($type == 'boolean') { if (isset ($default) && $default !== false) { // Having a default value of anything other than 'false' is pointless - ApiBase :: dieDebug("Boolean param $paramName's default is set to '$default'"); + ApiBase :: dieDebug(__METHOD__, "Boolean param $paramName's default is set to '$default'"); } $value = $wgRequest->getCheck($paramName); @@ -228,25 +227,26 @@ abstract class ApiBase { $value = is_array($value) ? array_map('intval', $value) : intval($value); break; case 'limit' : - if (!isset ($paramSettings[self::PARAM_MAX1]) || !isset ($paramSettings[self::PARAM_MAX2])) - ApiBase :: dieDebug("MAX1 or MAX2 are not defined for the limit $paramName"); + if (!isset ($paramSettings[self :: PARAM_MAX1]) || !isset ($paramSettings[self :: PARAM_MAX2])) + ApiBase :: dieDebug(__METHOD__, "MAX1 or MAX2 are not defined for the limit $paramName"); if ($multi) - ApiBase :: dieDebug("Multi-values not supported for $paramName"); - $min = isset ($paramSettings[self::PARAM_MIN]) ? $paramSettings[self::PARAM_MIN] : 0; + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); + $min = isset ($paramSettings[self :: PARAM_MIN]) ? $paramSettings[self :: PARAM_MIN] : 0; $value = intval($value); - $this->validateLimit($paramName, $value, $min, $paramSettings[self::PARAM_MAX1], $paramSettings[self::PARAM_MAX2]); + $this->validateLimit($paramName, $value, $min, $paramSettings[self :: PARAM_MAX1], $paramSettings[self :: PARAM_MAX2]); break; case 'boolean' : if ($multi) - ApiBase :: dieDebug("Multi-values not supported for $paramName"); + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); break; case 'timestamp' : if ($multi) - ApiBase :: dieDebug("Multi-values not supported for $paramName"); - $value = $this->prepareTimestamp($value); // Adds quotes around timestamp + ApiBase :: dieDebug(__METHOD__, "Multi-values not supported for $paramName"); + if (!preg_match('/^[0-9]{14}$/', $value)) + $this->dieUsage("Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$valueName}"); break; default : - ApiBase :: dieDebug("Param $paramName's type is unknown - $type"); + ApiBase :: dieDebug(__METHOD__, "Param $paramName's type is unknown - $type"); } } @@ -280,17 +280,6 @@ abstract class ApiBase { return $allowMultiple ? $valuesList : $valuesList[0]; } - /** - * Validate the proper format of the timestamp string (14 digits), and add quotes to it. - */ - function prepareTimestamp($value) { - if (preg_match('/^[0-9]{14}$/', $value)) { - return $this->db->addQuotes($value); - } else { - $this->dieUsage('Incorrect timestamp format', 'badtimestamp'); - } - } - /** * Validate the value against the minimum and user/bot maximum limits. Prints usage info on failure. */ @@ -305,10 +294,9 @@ abstract class ApiBase { if ($value > $botMax) { $this->dieUsage("$varname may not be over $botMax (set to $value) for bots", $varname); } - } else { - if ($value > $max) { - $this->dieUsage("$varname may not be over $max (set to $value) for users", $varname); - } + } + elseif ($value > $max) { + $this->dieUsage("$varname may not be over $max (set to $value) for users", $varname); } } @@ -322,8 +310,8 @@ abstract class ApiBase { /** * Internal code errors should be reported with this method */ - protected static function dieDebug($message) { - wfDebugDieBacktrace("Internal error: $message"); + protected static function dieDebug($method, $message) { + wfDebugDieBacktrace("Internal error in $method: $message"); } /** @@ -336,7 +324,7 @@ abstract class ApiBase { */ public function profileIn() { if ($this->mTimeIn !== 0) - ApiBase :: dieDebug(__METHOD__ . ' called twice without calling profileOut()'); + ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileOut()'); $this->mTimeIn = microtime(true); } @@ -345,9 +333,9 @@ abstract class ApiBase { */ public function profileOut() { if ($this->mTimeIn === 0) - ApiBase :: dieDebug(__METHOD__ . ' called without calling profileIn() first'); + ApiBase :: dieDebug(__METHOD__, 'called without calling profileIn() first'); if ($this->mDBTimeIn !== 0) - ApiBase :: dieDebug(__METHOD__ . ' must be called after database profiling is done with profileDBOut()'); + ApiBase :: dieDebug(__METHOD__, 'must be called after database profiling is done with profileDBOut()'); $this->mModuleTime += microtime(true) - $this->mTimeIn; $this->mTimeIn = 0; @@ -358,7 +346,7 @@ abstract class ApiBase { */ public function getProfileTime() { if ($this->mTimeIn !== 0) - ApiBase :: dieDebug(__METHOD__ . ' called without calling profileOut() first'); + ApiBase :: dieDebug(__METHOD__, 'called without calling profileOut() first'); return $this->mModuleTime; } @@ -372,9 +360,9 @@ abstract class ApiBase { */ public function profileDBIn() { if ($this->mTimeIn === 0) - ApiBase :: dieDebug(__METHOD__ . ' must be called while profiling the entire module with profileIn()'); + ApiBase :: dieDebug(__METHOD__, 'must be called while profiling the entire module with profileIn()'); if ($this->mDBTimeIn !== 0) - ApiBase :: dieDebug(__METHOD__ . ' called twice without calling profileDBOut()'); + ApiBase :: dieDebug(__METHOD__, 'called twice without calling profileDBOut()'); $this->mDBTimeIn = microtime(true); } @@ -383,9 +371,9 @@ abstract class ApiBase { */ public function profileDBOut() { if ($this->mTimeIn === 0) - ApiBase :: dieDebug(__METHOD__ . ' must be called while profiling the entire module with profileIn()'); + ApiBase :: dieDebug(__METHOD__, 'must be called while profiling the entire module with profileIn()'); if ($this->mDBTimeIn === 0) - ApiBase :: dieDebug(__METHOD__ . ' called without calling profileDBIn() first'); + ApiBase :: dieDebug(__METHOD__, 'called without calling profileDBIn() first'); $time = microtime(true) - $this->mDBTimeIn; $this->mDBTimeIn = 0; @@ -399,7 +387,7 @@ abstract class ApiBase { */ public function getProfileDBTime() { if ($this->mDBTimeIn !== 0) - ApiBase :: dieDebug(__METHOD__ . ' called without calling profileDBOut() first'); + ApiBase :: dieDebug(__METHOD__, 'called without calling profileDBOut() first'); return $this->mDBTime; } } diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php index bce60d9767..12048d0bdc 100644 --- a/includes/api/ApiFormatJson.php +++ b/includes/api/ApiFormatJson.php @@ -42,7 +42,7 @@ class ApiFormatJson extends ApiFormatBase { public function execute() { require ('ApiFormatJson_json.php'); $json = new Services_JSON(); - $this->printText($json->encode($this->getResult()->getData(), true)); + $this->printText($json->encode($this->getResultData(), true)); } protected function getDescription() { diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php index ad0655afb3..31edc8469e 100644 --- a/includes/api/ApiFormatXml.php +++ b/includes/api/ApiFormatXml.php @@ -99,38 +99,36 @@ class ApiFormatXml extends ApiFormatBase { foreach ($elemValue as $subElemId => & $subElemValue) { if (gettype($subElemId) === 'integer') { if (!is_array($subElemValue)) - ApiBase :: dieDebug(__FUNCTION__ . "($elemName, ...) has a scalar indexed value."); + ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has a scalar indexed value."); $indElements[] = $subElemValue; unset ($elemValue[$subElemId]); - } else - if (is_array($subElemValue)) { - $subElements[$subElemId] = $subElemValue; - unset ($elemValue[$subElemId]); - } + } elseif (is_array($subElemValue)) { + $subElements[$subElemId] = $subElemValue; + unset ($elemValue[$subElemId]); + } } if (is_null($subElemIndName) && !empty ($indElements)) - ApiBase :: dieDebug(__FUNCTION__ . "($elemName, ...) has integer keys without _element value"); + ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has integer keys without _element value"); if (!empty ($subElements) && !empty ($indElements) && !is_null($subElemContent)) - ApiBase :: dieDebug(__FUNCTION__ . "($elemName, ...) has content and subelements"); + ApiBase :: dieDebug(__METHOD__, "($elemName, ...) has content and subelements"); if (!is_null($subElemContent)) { $this->printText($indstr . wfElement($elemName, $elemValue, $subElemContent)); - } else - if (empty ($indElements) && empty ($subElements)) { + } elseif (empty ($indElements) && empty ($subElements)) { $this->printText($indstr . wfElement($elemName, $elemValue)); - } else { - $this->printText($indstr . wfElement($elemName, $elemValue, null)); + } else { + $this->printText($indstr . wfElement($elemName, $elemValue, null)); - foreach ($subElements as $subElemId => & $subElemValue) - $this->recXmlPrint($subElemId, $subElemValue, $indent); + foreach ($subElements as $subElemId => & $subElemValue) + $this->recXmlPrint($subElemId, $subElemValue, $indent); - foreach ($indElements as $subElemId => & $subElemValue) - $this->recXmlPrint($subElemIndName, $subElemValue, $indent); + foreach ($indElements as $subElemId => & $subElemValue) + $this->recXmlPrint($subElemIndName, $subElemValue, $indent); - $this->printText($indstr . wfCloseElement($elemName)); - } + $this->printText($indstr . wfCloseElement($elemName)); + } break; case 'object' : // ignore diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php index 8b74b0654a..ed52fb5db2 100644 --- a/includes/api/ApiFormatYaml.php +++ b/includes/api/ApiFormatYaml.php @@ -41,7 +41,7 @@ class ApiFormatYaml extends ApiFormatBase { public function execute() { require ('ApiFormatYaml_spyc.php'); - $this->printText(Spyc :: YAMLDump($this->getResult()->getData())); + $this->printText(Spyc :: YAMLDump($this->getResultData())); } protected function getDescription() { diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php index 80ca546d30..b90ab0c5c8 100644 --- a/includes/api/ApiHelp.php +++ b/includes/api/ApiHelp.php @@ -1,51 +1,51 @@ - - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - */ - -if (!defined('MEDIAWIKI')) { - // Eclipse helper - will be ignored in production - require_once ('ApiBase.php'); -} - -class ApiHelp extends ApiBase { - - public function __construct($main, $action) { - parent :: __construct($main); - } - - /** - * Stub module for displaying help when no parameters are given - */ - public function execute() { - $this->dieUsage('', 'help'); - } - - protected function getDescription() { - return array ( - 'Display this help screen.' - ); - } -} + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiBase.php'); +} + +class ApiHelp extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main); + } + + /** + * Stub module for displaying help when no parameters are given + */ + public function execute() { + $this->dieUsage('', 'help'); + } + + protected function getDescription() { + return array ( + 'Display this help screen.' + ); + } +} ?> \ No newline at end of file diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index b01691f708..d253804692 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -81,7 +81,7 @@ class ApiLogin extends ApiBase { $result['result'] = 'EmptyPass'; break; default : - $this->dieDebug('Unhandled case value'); + ApiBase :: dieDebug(__METHOD__, 'Unhandled case value'); } $this->getResult()->addValue(null, 'login', $result); diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 60e0ff09e8..767c7d93a2 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -57,12 +57,12 @@ class ApiMain extends ApiBase { protected function getAllowedParams() { return array ( 'format' => array ( - ApiBase::PARAM_DFLT => API_DEFAULT_FORMAT, - ApiBase::PARAM_TYPE => $this->mFormatNames + ApiBase :: PARAM_DFLT => API_DEFAULT_FORMAT, + ApiBase :: PARAM_TYPE => $this->mFormatNames ), 'action' => array ( - ApiBase::PARAM_DFLT => 'help', - ApiBase::PARAM_TYPE => $this->mModuleNames + ApiBase :: PARAM_DFLT => 'help', + ApiBase :: PARAM_TYPE => $this->mModuleNames ) ); } @@ -131,7 +131,7 @@ class ApiMain extends ApiBase { $data = array ( 'code' => $errorCode ); - ApiResult :: addContent($data, $this->makeHelpMsg()); + ApiResult :: setContent($data, $this->makeHelpMsg()); $this->mResult->addValue(null, 'error', $data); throw new UsageException($description, $errorCode); diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 1343c1ee4f..13c37514de 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -32,7 +32,7 @@ if (!defined('MEDIAWIKI')) { class ApiPageSet extends ApiQueryBase { private $mAllPages; // [ns][dbkey] => page_id or 0 when missing - private $mGoodTitles, $mMissingTitles, $mRedirectTitles, $mNormalizedTitles; + private $mGoodTitles, $mMissingTitles, $mMissingPageIDs, $mRedirectTitles, $mNormalizedTitles; private $mRequestedFields; @@ -42,6 +42,7 @@ class ApiPageSet extends ApiQueryBase { $this->mAllPages = array (); $this->mGoodTitles = array (); $this->mMissingTitles = array (); + $this->mMissingPageIDs = array (); $this->mRedirectTitles = array (); $this->mNormalizedTitles = array (); @@ -52,6 +53,10 @@ class ApiPageSet extends ApiQueryBase { $this->mRequestedFields[$fieldName] = null; } + public function getCustomField($fieldName) { + return $this->mRequestedFields[$fieldName]; + } + /** * Title objects that were found in the database. * @return array page_id (int) => Title (obj) @@ -68,6 +73,14 @@ class ApiPageSet extends ApiQueryBase { return $this->mMissingTitles; } + /** + * Page IDs that were not found in the database + * @return array of page IDs + */ + public function getMissingPageIDs() { + return $this->mMissingPageIDs; + } + /** * Get a list of redirects when doing redirect resolution * @return array prefixed_title (string) => prefixed_title (string) @@ -120,7 +133,10 @@ class ApiPageSet extends ApiQueryBase { * #5 Substitute the original LinkBatch object with the new list * #6 Repeat from step #1 */ - private function populateTitles($titles, $redirects) { + private function populatePages($titles, $pageids, $redirects) { + if (!is_null($titles) && !is_null($pageids)) + ApiBase :: dieDebug(__METHOD__, 'bad parameters'); + $processTitles = !is_null($titles); // Ensure we get minimum required fields $pageFlds = array ( @@ -135,20 +151,33 @@ class ApiPageSet extends ApiQueryBase { if ($redirects) $pageFlds['page_is_redirect'] = null; - $pageFlds = array_keys(array_merge($this->mRequestedFields, $pageFlds)); - - // Get validated and normalized title objects - $linkBatch = $this->processTitlesStrArray($titles); + $pageFlds = array_keys(array_merge($pageFlds, $this->mRequestedFields)); $db = $this->getDB(); + if ($processTitles) { + + // Get validated and normalized title objects + $linkBatch = $this->processTitlesStrArray($titles); + + $set = $linkBatch->constructSet('page', $db); + } else { + $set = array ( + 'page_id' => $pageids + ); + } + // // Repeat until all redirects have been resolved + // The infinite loop is prevented by keeping all known pages in $this->mAllPages // - while (false !== ($set = $linkBatch->constructSet('page', $db))) { - - // Hack: get the ns:titles stored in array(ns => array(titles)) format - $remaining = $linkBatch->data; + do { + if ($processTitles) { + // Hack: get the ns:titles stored in array(ns => array(titles)) format + $remaining = $linkBatch->data; + } else { + $remaining = array_flip($pageids); // turn pageids into keys + } $redirectIds = array (); @@ -160,25 +189,40 @@ class ApiPageSet extends ApiQueryBase { $this->profileDBOut(); while ($row = $db->fetchObject($res)) { - unset ($remaining[$row->page_namespace][$row->page_title]); + $pageId = intval($row->page_id); + + if ($processTitles) + unset ($remaining[$row->page_namespace][$row->page_title]); + else + unset ($remaining[$pageId]); + $title = Title :: makeTitle($row->page_namespace, $row->page_title); - $this->mAllPages[$row->page_namespace][$row->page_title] = $row->page_id; + $this->mAllPages[$row->page_namespace][$row->page_title] = $pageId; if ($redirects && $row->page_is_redirect == '1') { - $redirectIds[$row->page_id] = $title; + $redirectIds[$pageId] = $title; } else { - $this->mGoodTitles[$row->page_id] = $title; + $this->mGoodTitles[$pageId] = $title; + } + + foreach ($this->mRequestedFields as $fieldName => & $fieldValues) { + $fieldValues[$pageId] = $row-> $fieldName; } } $db->freeResult($res); - // - // The remaining titles in $remaining are non-existant pages - // - foreach ($remaining as $ns => $dbkeys) { - foreach ($dbkeys as $dbkey => $nothing) { - $this->mMissingTitles[] = Title :: makeTitle($ns, $dbkey); - $this->mAllPages[$ns][$dbkey] = 0; + if ($processTitles) { + // The remaining titles in $remaining are non-existant pages + foreach ($remaining as $ns => $dbkeys) { + foreach ($dbkeys as $dbkey => $nothing) { + $this->mMissingTitles[] = Title :: makeTitle($ns, $dbkey); + $this->mAllPages[$ns][$dbkey] = 0; + } + } + } else { + // The remaining pageids in $remaining do not exist + foreach ($remaining as $pageid => $ignore) { + $this->mMissingPageIDs[] = $pageid; } } @@ -187,44 +231,75 @@ class ApiPageSet extends ApiQueryBase { // // Resolve redirects by querying the pagelinks table, and repeat the process - // - // Create a new linkBatch object for the next pass - $linkBatch = new LinkBatch(); - - // find redirect targets for all redirect pages - $this->profileDBIn(); - $res = $db->select('pagelinks', array ( - 'pl_from', - 'pl_namespace', - 'pl_title' - ), array ( - 'pl_from' => array_keys($redirectIds - )), __METHOD__); - $this->profileDBOut(); - - while ($row = $db->fetchObject($res)) { + // + $linkBatch = $this->ResolveRedirectList($redirectIds); - // Bug 7304 workaround - // ( http://bugzilla.wikipedia.org/show_bug.cgi?id=7304 ) - // A redirect page may have more than one link. - // This code will only use the first link returned. - if (isset ($redirectIds[$row->pl_from])) { // remove line when 7304 is fixed + // Redirects are always titles + $processTitles = true; + } + while (false !== ($set = $linkBatch->constructSet('page', $db))); + } - $titleStrFrom = $redirectIds[$row->pl_from]->getPrefixedText(); - $titleStrTo = Title :: makeTitle($row->pl_namespace, $row->pl_title)->getPrefixedText(); - $this->mRedirectTitles[$titleStrFrom] = $titleStrTo; + private function ResolveRedirectList($redirectIds) { - unset ($redirectIds[$row->pl_from]); // remove line when 7304 is fixed + $linkBatch = new LinkBatch(); + $db = $this->getDB(); - // Avoid an infinite loop by checking if we have already processed this target - if (!isset ($this->mAllPages[$row->pl_namespace][$row->pl_title])) { - $linkBatch->add($row->pl_namespace, $row->pl_title); - } + // find redirect targets for all redirect pages + $this->profileDBIn(); + $res = $db->select('pagelinks', array ( + 'pl_from', + 'pl_namespace', + 'pl_title' + ), array ( + 'pl_from' => array_keys($redirectIds + )), __METHOD__); + $this->profileDBOut(); + + while ($row = $db->fetchObject($res)) { + + $plfrom = intval($row->pl_from); + + // Bug 7304 workaround + // ( http://bugzilla.wikipedia.org/show_bug.cgi?id=7304 ) + // A redirect page may have more than one link. + // This code will only use the first link returned. + if (isset ($redirectIds[$plfrom])) { // remove line when bug 7304 is fixed + + $titleStrFrom = $redirectIds[$plfrom]->getPrefixedText(); + $titleStrTo = Title :: makeTitle($row->pl_namespace, $row->pl_title)->getPrefixedText(); + unset ($redirectIds[$plfrom]); // remove line when bug 7304 is fixed + + // Avoid an infinite loop by checking if we have already processed this target + if (!isset ($this->mAllPages[$row->pl_namespace][$row->pl_title])) { + $linkBatch->add($row->pl_namespace, $row->pl_title); + } + } else { + // This redirect page has more than one link. + // This is very slow, but safer until bug 7304 is resolved + $title = Title :: newFromID($plfrom); + $titleStrFrom = $title->getPrefixedText(); + + $article = new Article($title); + $text = $article->getContent(); + $titleTo = Title :: newFromRedirect($text); + $titleStrTo = $titleTo->getPrefixedText(); + + if (is_null($titleStrTo)) + ApiBase :: dieDebug(__METHOD__, 'Bug7304 workaround: redir target from {$title->getPrefixedText()} not found'); + + // Avoid an infinite loop by checking if we have already processed this target + if (!isset ($this->mAllPages[$titleTo->getNamespace()][$titleTo->getDBkey()])) { + $linkBatch->addObj($titleTo); } } - $db->freeResult($res); + + $this->mRedirectTitles[$titleStrFrom] = $titleStrTo; } + $db->freeResult($res); + + return $linkBatch; } /** @@ -262,7 +337,7 @@ class ApiPageSet extends ApiQueryBase { return $linkBatch; } - private function populatePageIDs($pageids) { + private function populateRevIDs($revids) { $this->dieUsage(__METHOD__ . ' is not implemented', 'notimplemented'); } @@ -287,10 +362,8 @@ class ApiPageSet extends ApiQueryBase { switch ($dataSource) { case 'titles' : - $this->populateTitles($titles, $redirects); - break; case 'pageids' : - $this->populatePageIDs($pageids, $redirects); + $this->populatePages($titles, $pageids, $redirects); break; case 'revids' : $this->populateRevIDs($revids); @@ -304,15 +377,15 @@ class ApiPageSet extends ApiQueryBase { protected function getAllowedParams() { return array ( 'titles' => array ( - ApiBase::PARAM_ISMULTI => true + ApiBase :: PARAM_ISMULTI => true ), 'pageids' => array ( - ApiBase::PARAM_TYPE => 'integer', - ApiBase::PARAM_ISMULTI => true + ApiBase :: PARAM_TYPE => 'integer', + ApiBase :: PARAM_ISMULTI => true ), 'revids' => array ( - ApiBase::PARAM_TYPE => 'integer', - ApiBase::PARAM_ISMULTI => true + ApiBase :: PARAM_TYPE => 'integer', + ApiBase :: PARAM_ISMULTI => true ), 'redirects' => false ); diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 458007b8cd..533c95cd2a 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -31,13 +31,8 @@ if (!defined('MEDIAWIKI')) { class ApiQuery extends ApiBase { - private $mMetaModuleNames, $mPropModuleNames, $mListModuleNames; - private $mData; - - private $mQueryMetaModules = array ( - 'siteinfo' => 'ApiQuerySiteinfo' - ); - // 'userinfo' => 'ApiQueryUserinfo', + private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames; + private $mPageSet; private $mQueryPropModules = array ( 'info' => 'ApiQueryInfo', @@ -62,13 +57,18 @@ class ApiQuery extends ApiBase { // 'users' => 'ApiQueryUsers', // 'watchlist' => 'ApiQueryWatchlist', + private $mQueryMetaModules = array ( + 'siteinfo' => 'ApiQuerySiteinfo' + ); + // 'userinfo' => 'ApiQueryUserinfo', + private $mSlaveDB = null; public function __construct($main, $action) { parent :: __construct($main); - $this->mMetaModuleNames = array_keys($this->mQueryMetaModules); $this->mPropModuleNames = array_keys($this->mQueryPropModules); $this->mListModuleNames = array_keys($this->mQueryListModules); + $this->mMetaModuleNames = array_keys($this->mQueryMetaModules); // Allow the entire list of modules at first, // but during module instantiation check if it can be used as a generator. @@ -81,8 +81,8 @@ class ApiQuery extends ApiBase { return $this->mSlaveDB; } - public function getData() { - return $this->mData; + public function getPageSet() { + return $this->mPageSet; } /** @@ -96,13 +96,13 @@ class ApiQuery extends ApiBase { * #5 Execute all requested modules */ public function execute() { - $meta = $prop = $list = $generator = null; + $prop = $list = $meta = $generator = null; extract($this->extractRequestParams()); // // Create PageSet // - $this->mData = new ApiPageSet($this); + $this->mPageSet = new ApiPageSet($this); // // If generator is provided, get a new dataset to work on @@ -111,29 +111,50 @@ class ApiQuery extends ApiBase { $this->executeGenerator($generator); // Instantiate required modules - // During instantiation, modules may optimize data requests through the $this->mData object - // $this->mData will be lazy loaded when modules begin to request data during execution $modules = array (); - if (isset ($meta)) - foreach ($meta as $moduleName) - $modules[] = new $this->mQueryMetaModules[$moduleName] ($this, $moduleName); if (isset ($prop)) foreach ($prop as $moduleName) $modules[] = new $this->mQueryPropModules[$moduleName] ($this, $moduleName); if (isset ($list)) foreach ($list as $moduleName) $modules[] = new $this->mQueryListModules[$moduleName] ($this, $moduleName); + if (isset ($meta)) + foreach ($meta as $moduleName) + $modules[] = new $this->mQueryMetaModules[$moduleName] ($this, $moduleName); + + // Modules may optimize data requests through the $this->getPageSet() object + // Execute all requested modules. + foreach ($modules as $module) { + $module->requestExtraData(); + } // // Get page information for the given pageSet // - $this->mData->profileIn(); - $this->mData->execute(); - $this->mData->profileOut(); + $this->mPageSet->profileIn(); + $this->mPageSet->execute(); + $this->mPageSet->profileOut(); + + // + // Record page information + // + $this->outputGeneralPageInfo(); + + // Execute all requested modules. + foreach ($modules as $module) { + $module->profileIn(); + $module->execute(); + $module->profileOut(); + } + } + + private function outputGeneralPageInfo() { + + $pageSet = $this->getPageSet(); // Title normalizations $normValues = array (); - foreach ($this->mData->getNormalizedTitles() as $rawTitleStr => $titleStr) { + foreach ($pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr) { $normValues[] = array ( 'from' => $rawTitleStr, 'to' => $titleStr @@ -147,7 +168,7 @@ class ApiQuery extends ApiBase { // Show redirect information $redirValues = array (); - foreach ($this->mData->getRedirectTitles() as $titleStrFrom => $titleStrTo) { + foreach ($pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo) { $redirValues[] = array ( 'from' => $titleStrFrom, 'to' => $titleStrTo @@ -159,17 +180,35 @@ class ApiQuery extends ApiBase { $this->getResult()->addValue('query', 'redirects', $redirValues); } - // Execute all requested modules. - foreach ($modules as $module) { - $module->profileIn(); - $module->execute(); - $module->profileOut(); + // + // Page elements + // + $pages = array (); + + // Report any missing titles + $fakepageid = -1; + foreach ($pageSet->getMissingTitles() as $title) { + $pages[$fakepageid--] = array ( + 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'missing' => ''); } - // Ensure that pages are shown as '' elements - $data = & $this->getResultData(); - if (isset ($data['query']['pages'])) { - ApiResult :: setIndexedTagName($data['query']['pages'], 'page'); + // Report any missing page ids + foreach ($pageSet->getMissingPageIDs() as $pageid) { + $pages[$pageid] = array ( + 'id' => $pageid, + 'missing' => '' + ); + } + + // Output general page information for found titles + foreach ($pageSet->getGoodTitles() as $pageid => $title) { + $pages[$pageid] = array ( + 'ns' => $title->getNamespace(), 'title' => $title->getPrefixedText(), 'id' => $pageid); + } + + if (!empty ($pages)) { + ApiResult :: setIndexedTagName($pages, 'page'); + $this->getResult()->addValue('query', 'pages', $pages); } } @@ -178,20 +217,19 @@ class ApiQuery extends ApiBase { // Find class that implements requested generator if (isset ($this->mQueryListModules[$generator])) $className = $this->mQueryListModules[$generator]; + elseif (isset ($this->mQueryPropModules[$generator])) $className = $this->mQueryPropModules[$generator]; else - if (isset ($this->mQueryPropModules[$generator])) - $className = $this->mQueryPropModules[$generator]; - else - ApiBase :: dieDebug("Unknown generator=$generator"); + ApiBase :: dieDebug(__METHOD__, "Unknown generator=$generator"); $module = new $className ($this, $generator, true); + $module->requestExtraData(); // execute pageSet here to get the data required by the generator module - $this->mData->profileIn(); - $this->mData->execute(); - $this->mData->profileOut(); + $this->mPageSet->profileIn(); + $this->mPageSet->execute(); + $this->mPageSet->profileOut(); - // change $this->mData + // change $this->mPageSet // TODO: implement $this->dieUsage('Generator execution has not been implemented', 'notimplemented'); @@ -199,17 +237,17 @@ class ApiQuery extends ApiBase { protected function getAllowedParams() { return array ( - 'meta' => array ( - ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => $this->mMetaModuleNames - ), 'prop' => array ( - ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => $this->mPropModuleNames + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => $this->mPropModuleNames ), 'list' => array ( - ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => $this->mListModuleNames + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => $this->mListModuleNames + ), + 'meta' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => $this->mMetaModuleNames ) // 'generator' => array ( // ApiBase::PARAM_TYPE => $this->mAllowedGenerators @@ -229,34 +267,35 @@ class ApiQuery extends ApiBase { // Make sure the internal object is empty // (just in case a sub-module decides to optimize during instantiation) - $this->mData = null; + $this->mPageSet = null; $astriks = str_repeat('--- ', 8); - $msg .= "\n$astriks Query: Meta $astriks\n\n"; - $msg .= $this->makeHelpMsgHelper($this->mQueryMetaModules, 'meta'); $msg .= "\n$astriks Query: Prop $astriks\n\n"; $msg .= $this->makeHelpMsgHelper($this->mQueryPropModules, 'prop'); $msg .= "\n$astriks Query: List $astriks\n\n"; $msg .= $this->makeHelpMsgHelper($this->mQueryListModules, 'list'); + $msg .= "\n$astriks Query: Meta $astriks\n\n"; + $msg .= $this->makeHelpMsgHelper($this->mQueryMetaModules, 'meta'); return $msg; } private function makeHelpMsgHelper($moduleList, $paramName) { - $msg = ''; + + $moduleDscriptions = array (); foreach ($moduleList as $moduleName => $moduleClass) { - $msg .= "* $paramName=$moduleName *"; + $msg = "* $paramName=$moduleName *"; $module = new $moduleClass ($this, $moduleName, null); $msg2 = $module->makeHelpMsg(); if ($msg2 !== false) $msg .= $msg2; - $msg .= "\n"; if ($module->getCanGenerate()) - $msg .= " * Can be used as a generator\n"; + $msg .= "Generator:\n This module may be used as a generator\n"; + $moduleDscriptions[] = $msg; } - return $msg; + return implode("\n", $moduleDscriptions); } /** @@ -269,12 +308,10 @@ class ApiQuery extends ApiBase { protected function getParamDescription() { return array ( - 'meta' => 'Which meta data to get about the site', 'prop' => 'Which properties to get for the titles/revisions/pageids', 'list' => 'Which lists to get', - 'generator' => 'Use the output of a list as the input for other prop/list/meta items', - - + 'meta' => 'Which meta data to get about the site', + 'generator' => 'Use the output of a list as the input for other prop/list/meta items' ); } diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index db7f9ef067..6c519c5d6d 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -48,9 +48,7 @@ class ApiQueryAllpages extends ApiQueryBase { if ($apfilterredir === 'redirects') $where['page_is_redirect'] = 1; - else - if ($apfilterredir === 'nonredirects') - $where['page_is_redirect'] = 0; + elseif ($apfilterredir === 'nonredirects') $where['page_is_redirect'] = 0; $this->profileDBIn(); $res = $db->select('page', array ( @@ -107,23 +105,23 @@ class ApiQueryAllpages extends ApiQueryBase { return array ( 'apfrom' => null, 'apnamespace' => array ( - ApiBase::PARAM_DFLT => 0, - ApiBase::PARAM_TYPE => $validNamespaces + ApiBase :: PARAM_DFLT => 0, + ApiBase :: PARAM_TYPE => $validNamespaces ), 'apfilterredir' => array ( - ApiBase::PARAM_DFLT => 'all', - ApiBase::PARAM_TYPE => array ( + ApiBase :: PARAM_DFLT => 'all', + ApiBase :: PARAM_TYPE => array ( 'all', 'redirects', 'nonredirects' ) ), 'aplimit' => array ( - ApiBase::PARAM_DFLT => 10, - ApiBase::PARAM_TYPE => 'limit', - ApiBase::PARAM_MIN => 1, - ApiBase::PARAM_MAX1 => 500, - ApiBase::PARAM_MAX2 => 5000 + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX1 => 500, + ApiBase :: PARAM_MAX2 => 5000 ) ); } diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index 10ebc13a88..927af18953 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -1,93 +1,100 @@ - - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - */ - -if (!defined('MEDIAWIKI')) { - // Eclipse helper - will be ignored in production - require_once ('ApiBase.php'); -} - -abstract class ApiQueryBase extends ApiBase { - - private $mQueryModule, $mModuleName, $mGenerator; - - public function __construct($query, $moduleName, $generator = false) { - parent :: __construct($query->getMain()); - $this->mQueryModule = $query; - $this->mModuleName = $moduleName; - $this->mGenerator = $generator; - } - - /** - * Get the main Query module - */ - public function getQuery() { - return $this->mQueryModule; - } - - /** - * Get the name of the query being executed by this instance - */ - public function getModuleName() { - return $this->mModuleName; - } - - /** - * Get the Query database connection (readonly) - */ - protected function getDB() { - return $this->getQuery()->getDB(); - } - - /** - * Get the PageSet object to work on - * @return ApiPageSet data - */ - protected function getData() { - return $this->mQueryModule->getData(); - } - - /** - * Return true if this instance is being used as a generator. - */ - protected function getIsGenerator() { - return $this->mGenerator; - } - - /** - * Derived classes return true when they can be used as title generators for other query modules. - */ - public function getCanGenerate() { - return false; - } - - public static function titleToKey($title) { - return str_replace(' ', '_', $title); - } - public static function keyToTitle($key) { - return str_replace('_', ' ', $key); - } -} -?> + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiBase.php'); +} + +abstract class ApiQueryBase extends ApiBase { + + private $mQueryModule, $mModuleName, $mGenerator; + + public function __construct($query, $moduleName, $generator = false) { + parent :: __construct($query->getMain()); + $this->mQueryModule = $query; + $this->mModuleName = $moduleName; + $this->mGenerator = $generator; + } + + /** + * Override this method to request extra fields from the pageSet + * using $this->getPageSet()->requestField('fieldName') + */ + public function requestExtraData() { + } + + /** + * Get the main Query module + */ + public function getQuery() { + return $this->mQueryModule; + } + + /** + * Get the name of the query being executed by this instance + */ + public function getModuleName() { + return $this->mModuleName; + } + + /** + * Get the Query database connection (readonly) + */ + protected function getDB() { + return $this->getQuery()->getDB(); + } + + /** + * Get the PageSet object to work on + * @return ApiPageSet data + */ + protected function getPageSet() { + return $this->mQueryModule->getPageSet(); + } + + /** + * Return true if this instance is being used as a generator. + */ + protected function getIsGenerator() { + return $this->mGenerator; + } + + /** + * Derived classes return true when they can be used as title generators for other query modules. + */ + public function getCanGenerate() { + return false; + } + + public static function titleToKey($title) { + return str_replace(' ', '_', $title); + } + public static function keyToTitle($key) { + return str_replace('_', ' ', $key); + } +} +?> diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index dccad670e2..2a1e059a58 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -31,39 +31,49 @@ if (!defined('MEDIAWIKI')) { class ApiQueryInfo extends ApiQueryBase { + // private $mParameters; + public function __construct($query, $moduleName, $generator = false) { parent :: __construct($query, $moduleName, $generator); } + public function requestExtraData() { + $pageSet = $this->getPageSet(); + $pageSet->requestField('page_is_redirect'); + $pageSet->requestField('page_touched'); + $pageSet->requestField('page_latest'); + } + public function execute() { - } + $pageSet = $this->getPageSet(); + $titles = $pageSet->getGoodTitles(); + $result = & $this->getResult(); - protected function getAllowedParams() { - return array ( - 'param' => 'default', - 'enumparam' => array ( - ApiBase::PARAM_DFLT => 'default', - ApiBase::PARAM_ISMULTI => false, - ApiBase::PARAM_TYPE => array ( - 'a', - 'b' - ) - ) - ); - } + $pageIsRedir = $pageSet->getCustomField('page_is_redirect'); + $pageTouched = $pageSet->getCustomField('page_touched'); + $pageLatest = $pageSet->getCustomField('page_latest'); + + foreach ($titles as $pageid => $title) { + $pageInfo = array ('touched' => $pageTouched[$pageid], 'lastrevid' => $pageLatest[$pageid]); + + if ($pageIsRedir[$pageid]) + $pageInfo['redirect'] = ''; - protected function getParamDescription() { - return array (); + $result->addValue(array ( + 'query', + 'pages' + ), $pageid, $pageInfo); + } } protected function getDescription() { - return 'module a'; + return 'Get basic page information such as namespace, title, last touched date, ...'; } protected function getExamples() { return array ( - 'http://...' + 'api.php?action=query&prop=info&titles=Main%20Page' ); } } diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index e8e6efb4d8..7a12ce71eb 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -39,6 +39,8 @@ class ApiQueryRevisions extends ApiQueryBase { $rvlimit = $rvstartid = $rvendid = $rvstart = $rvend = $rvdir = $rvprop = null; extract($this->extractRequestParams()); + $db = $this->getDB(); + // true when ordered by timestamp from older to newer, false otherwise $dirNewer = ($rvdir === 'newer'); @@ -48,9 +50,9 @@ class ApiQueryRevisions extends ApiQueryBase { // difficult to manage continuations and require additional sql indexes $enumRevMode = ($rvlimit !== 0 || $rvstartid !== 0 || $rvendid !== 0 || $dirNewer || isset ($rvstart) || isset ($rvend)); - $data = $this->getData(); - $pageCount = $data->getGoodTitleCount(); - $revCount = $data->getRevisionCount(); + $pageSet = $this->getPageSet(); + $pageCount = $pageSet->getGoodTitleCount(); + $revCount = $pageSet->getRevisionCount(); // Optimization -- nothing to do if ($revCount === 0 && $pageCount === 0) @@ -108,7 +110,7 @@ class ApiQueryRevisions extends ApiQueryBase { $showContent = true; break; default : - ApiBase :: dieDebug("unknown rvprop $prop"); + ApiBase :: dieDebug(__METHOD__, "unknown rvprop $prop"); } } } @@ -134,9 +136,9 @@ class ApiQueryRevisions extends ApiQueryBase { if ($rvendid !== 0) $conds[] = 'rev_id' . $before . intval($rvendid); if (isset ($rvstart)) - $conds[] = 'rev_timestamp' . $after . $this->prepareTimestamp($rvstart); + $conds[] = 'rev_timestamp' . $after . $db->addQuotes($rvstart); if (isset ($rvend)) - $conds[] = 'rev_timestamp' . $before . $this->prepareTimestamp($rvend); + $conds[] = 'rev_timestamp' . $before . $db->addQuotes($rvend); // must manually initialize unset rvlimit if (!isset ($rvlimit)) @@ -145,35 +147,34 @@ class ApiQueryRevisions extends ApiQueryBase { $this->validateLimit('rvlimit', $rvlimit, 1, $userMax, $botMax); // There is only one ID, use it - $conds['rev_page'] = array_pop(array_keys($data->getGoodTitles())); - - } else - if ($pageCount > 0) { - // When working in multi-page non-enumeration mode, - // limit to the latest revision only - $tables[] = 'page'; - $conds[] = 'page_id=rev_page'; - $conds[] = 'page_latest=rev_id'; - $this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax); + $conds['rev_page'] = array_pop(array_keys($pageSet->getGoodTitles())); - // Get all page IDs - $conds['page_id'] = array_keys($data->getGoodTitles()); - - $rvlimit = $pageCount; // assumption testing -- we should never get more then $pageCount rows. - } else - if ($revCount > 0) { - $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax); + } + elseif ($pageCount > 0) { + // When working in multi-page non-enumeration mode, + // limit to the latest revision only + $tables[] = 'page'; + $conds[] = 'page_id=rev_page'; + $conds[] = 'page_latest=rev_id'; + $this->validateLimit('page_count', $pageCount, 1, $userMax, $botMax); + + // Get all page IDs + $conds['page_id'] = array_keys($pageSet->getGoodTitles()); + + $rvlimit = $pageCount; // assumption testing -- we should never get more then $pageCount rows. + } + elseif ($revCount > 0) { + $this->validateLimit('rev_count', $revCount, 1, $userMax, $botMax); - // Get all revision IDs - $conds['rev_id'] = array_keys($data->getRevisionIDs()); + // Get all revision IDs + $conds['rev_id'] = array_keys($pageSet->getRevisionIDs()); - $rvlimit = $revCount; // assumption testing -- we should never get more then $revCount rows. - } else - ApiBase :: dieDebug('param validation?'); + $rvlimit = $revCount; // assumption testing -- we should never get more then $revCount rows. + } else + ApiBase :: dieDebug(__METHOD__, 'param validation?'); $options['LIMIT'] = $rvlimit +1; - $db = $this->getDB(); $this->profileDBIn(); $res = $db->select($tables, $fields, $conds, __METHOD__, $options); $this->profileDBOut(); @@ -185,7 +186,7 @@ class ApiQueryRevisions extends ApiQueryBase { if (++ $count > $rvlimit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... if (!$enumRevMode) - ApiBase :: dieDebug('Got more rows then expected'); // bug report + ApiBase :: dieDebug(__METHOD__, 'Got more rows then expected'); // bug report $startStr = 'rvstartid=' . $row->rev_id; $msg = array ( @@ -216,7 +217,7 @@ class ApiQueryRevisions extends ApiQueryBase { $vals['comment'] = $row->rev_comment; if ($showContent) { - ApiResult :: addContent($vals, Revision :: getRevisionText($row)); + ApiResult :: setContent($vals, Revision :: getRevisionText($row)); } $this->getResult()->addValue(array ( @@ -230,7 +231,7 @@ class ApiQueryRevisions extends ApiQueryBase { // Ensure that all revisions are shown as '' elements $data = & $this->getResultData(); foreach ($data['query']['pages'] as & $page) { - if (isset ($page['revisions'])) { + if (is_array($page) && array_key_exists('revisions', $page)) { ApiResult :: setIndexedTagName($page['revisions'], 'rev'); } } @@ -238,53 +239,73 @@ class ApiQueryRevisions extends ApiQueryBase { protected function getAllowedParams() { return array ( + 'rvprop' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( + 'timestamp', + 'user', + 'comment', + 'content' + ) + ), 'rvlimit' => array ( - ApiBase::PARAM_DFLT => 0, - ApiBase::PARAM_TYPE => 'limit', - ApiBase::PARAM_MIN => 0, - ApiBase::PARAM_MAX1 => 50, - ApiBase::PARAM_MAX2 => 500 + ApiBase :: PARAM_DFLT => 0, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 0, + ApiBase :: PARAM_MAX1 => 50, + ApiBase :: PARAM_MAX2 => 500 ), 'rvstartid' => 0, 'rvendid' => 0, 'rvstart' => array ( - ApiBase::PARAM_TYPE => 'timestamp' + ApiBase :: PARAM_TYPE => 'timestamp' ), 'rvend' => array ( - ApiBase::PARAM_TYPE => 'timestamp' + ApiBase :: PARAM_TYPE => 'timestamp' ), 'rvdir' => array ( - ApiBase::PARAM_DFLT => 'older', - ApiBase::PARAM_TYPE => array ( + ApiBase :: PARAM_DFLT => 'older', + ApiBase :: PARAM_TYPE => array ( 'newer', 'older' ) - ), - 'rvprop' => array ( - ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => array ( - 'timestamp', - 'user', - 'comment', - 'content' - ) ) ); } + protected function getParamDescription() { + return array ( + 'rvprop' => 'Which properties to get for each revision: user|timestamp|comment|content', + 'rvlimit' => 'limit how many revisions will be returned (enum)', + 'rvstartid' => 'from which revision id to start enumeration (enum)', + 'rvendid' => 'stop revision enumeration on this revid (enum)', + 'rvstart' => 'from which revision timestamp to start enumeration (enum)', + 'rvend' => 'enumerate up to this timestamp (enum)', + 'rvdir' => 'direction of enumeration - towards "newer" or "older" revisions (enum)' + ); + } + protected function getDescription() { return array ( 'Get revision information.', 'This module may be used in several ways:', ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter.', ' 2) Get revisions for one given page, by using titles/pageids with rvstart/rvend/rvlimit params.', - ' 3) Get data about a set of revisions by setting their IDs with revids parameter.' + ' 3) Get data about a set of revisions by setting their IDs with revids parameter.', + 'All parameters marked as (enum) may only be used with a single page (#2).' ); } protected function getExamples() { return array ( - 'api.php?action=query&prop=revisions&titles=Main%20Page&rvprop=timestamp|user|comment|content' + 'Get data with content for the last revision of titles "API" and "Main Page":', + ' api.php?action=query&prop=revisions&titles=API|Main%20Page&rvprop=timestamp|user|comment|content', + 'Get last 5 revisions of the "Main Page":', + ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment', + 'Get first 5 revisions of the "Main Page":', + ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer', + 'Get first 5 revisions of the "Main Page" made after 2006-05-01:', + ' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000' ); } } diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index 6bddee2a8f..0ec37dddd4 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -63,14 +63,14 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data[$ns] = array ( 'id' => $ns ); - ApiResult :: addContent($data[$ns], $title); + ApiResult :: setContent($data[$ns], $title); } ApiResult :: setIndexedTagName($data, 'ns'); $this->getResult()->addValue('query', $prop, $data); break; default : - ApiBase :: dieDebug("Unknown siprop=$prop"); + ApiBase :: dieDebug(__METHOD__, "Unknown siprop=$prop"); } } } @@ -78,9 +78,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { protected function getAllowedParams() { return array ( 'siprop' => array ( - ApiBase::PARAM_DFLT => 'general', - ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => array ( + ApiBase :: PARAM_DFLT => 'general', + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array ( 'general', 'namespaces' ) diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index 3e65b57937..55cdafb6f4 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -53,27 +53,36 @@ class ApiResult extends ApiBase { * Add an output value to the array by name. * Verifies that value with the same name has not been added before. */ - public static function addElement(& $arr, $name, $value) { + public static function setElement(& $arr, $name, $value) { if ($arr === null || $name === null || $value === null || !is_array($arr) || is_array($name)) - ApiBase :: dieDebug('Bad parameter for ' . __METHOD__); - if (isset ($arr[$name])) - ApiBase :: dieDebug("Attempting to add element $name=$value, existing value is {$arr[$name]}"); - $arr[$name] = $value; + ApiBase :: dieDebug(__METHOD__, 'Bad parameter'); + + if (!isset ($arr[$name])) { + $arr[$name] = $value; + } + elseif (is_array($arr[$name]) && is_array($value)) { + $merged = array_intersect_key($arr[$name], $value); + if (empty ($merged)) + $arr[$name] += $value; + else + ApiBase :: dieDebug(__METHOD__, "Attempting to merge element $name"); + } else + ApiBase :: dieDebug(__METHOD__, "Attempting to add element $name=$value, existing value is {$arr[$name]}"); } /** * Adds the content element to the array. * Use this function instead of hardcoding the '*' element. */ - public static function addContent(& $arr, $value) { + public static function setContent(& $arr, $value) { if (is_array($value)) - ApiBase :: dieDebug('Bad parameter for ' . __METHOD__); - ApiResult :: addElement($arr, '*', $value); + ApiBase :: dieDebug(__METHOD__, 'Bad parameter'); + ApiResult :: setElement($arr, '*', $value); } // public static function makeContentElement($tag, $value) { // $result = array(); - // ApiResult::addContent($result, ) + // ApiResult::setContent($result, ) // } // /** @@ -81,9 +90,9 @@ class ApiResult extends ApiBase { * all indexed values will have the given tag name. */ public static function setIndexedTagName(& $arr, $tag) { - // Do not use addElement() as it is ok to call this more than once + // Do not use setElement() as it is ok to call this more than once if ($arr === null || $tag === null || !is_array($arr) || is_array($tag)) - ApiBase :: dieDebug('Bad parameter for ' . __METHOD__); + ApiBase :: dieDebug(__METHOD__, 'Bad parameter'); $arr['_element'] = $tag; } @@ -110,7 +119,7 @@ class ApiResult extends ApiBase { } } - ApiResult :: addElement($data, $name, $value); + ApiResult :: setElement($data, $name, $value); } /** @@ -127,9 +136,6 @@ class ApiResult extends ApiBase { if ($key[0] === '_') { unset ($data[$key]); } - elseif ($key === '*' && $value === '') { - unset ($data[$key]); - } elseif (is_array($value)) { ApiResult :: SanitizeDataInt($value); } @@ -137,7 +143,7 @@ class ApiResult extends ApiBase { } public function execute() { - $this->dieDebug('execute() is not supported on Result object'); + ApiBase :: dieDebug(__METHOD__, 'execute() is not supported on Result object'); } } -?> +?> \ No newline at end of file -- 2.20.1